﻿
Imports System.IO
Imports std

Public MustInherit Class cacheProcessor
    Inherits requestProcessor

    Public Class cachefilters
        Public Const language As String = "lang"
    End Class

    Private Class cache_t
        Implements ICloneable

        Public b As bytes
        Public m As map(Of String, list(Of String))

        Public Sub New(ByVal b As bytes, ByVal m As map(Of String, list(Of String)))
            Me.b = b
            Me.m = m
        End Sub

        Public Function Clone() As Object Implements ICloneable.Clone
            Return Me
        End Function
    End Class

    Public Const defaultForceUpdate As String = "nocache"

    Protected MustOverride Function processImpl(ByVal context As httpContext) As Boolean
    Private cache As cacheManager(Of String, cache_t) = Nothing
    Private forceUpdate As String = Nothing

    Public Enum cachetypeEnum
        lang_urlfull
        urlfull
        pathquery
        none
    End Enum

    Protected Overridable Function cachetype() As cacheTypeEnum
        Return cachetypeEnum.lang_urlfull
    End Function

    Public Sub clearcache()
        cache.clear()
    End Sub

    Protected Overridable Function cachesize() As Int64
        Return cacheManager(Of String, bytes).defaultCacheSize
    End Function

    Protected Overridable Function debugmodeRetiredTicks() As Int64
        Return 1
    End Function

    Protected Overridable Function normalRetiredTicks() As Int64
        Return secondsToTicks(30 * 60)
    End Function

    Protected Function retiredticks() As Int64
        If isdebugbuild() Then
            Return debugmodeRetiredTicks()
        Else
            Return normalRetiredTicks()
        End If
    End Function

    Private Function readcache(ByVal cachekey As String,
                               ByRef b As bytes,
                               ByRef m As map(Of String, list(Of String))) As Boolean
        Dim ct As cache_t = Nothing
        If cache.get(cachekey, ct) Then
            b = ct.b
            m = ct.m
            Return True
        Else
            Return False
        End If
    End Function

#If RETIRED Then
    Protected Overridable Sub addresponseHeaders(ByVal context As httpContext)
    End Sub
#End If

    Public NotOverridable Overrides Function process(ByVal context As httpContext) As Boolean
        assert(Not context Is Nothing, "context is nothing.")

        Dim cachekey As String = Nothing
        If cachetype() = cachetypeEnum.lang_urlfull Then
            Dim lang As String = Nothing
            languageSeparate(context.language(), Nothing, lang)
            If isemptystring(lang) Then
                lang = stringsPackage.defaultLanguage
            End If
            'right slash should not be in a url
            cachekey = context.url().full() + character.rightSlash + _
                       cachefilters.language + character.equalSign + lang
        ElseIf cachetype() = cachetypeEnum.urlfull Then
            cachekey = context.url().full()
        ElseIf cachetype() = cachetypeEnum.pathquery Then
            cachekey = context.url().pathquery()
        Else
            cachekey = emptyString
        End If

        Dim buff As bytes = Nothing
        Dim m As map(Of String, list(Of String)) = Nothing
        If context.url().queryExist(forceUpdate) OrElse _
           Not readcache(cachekey, buff, m) Then
            If Not processImpl(context) Then
                raiseError("cannot process request " + context.url.full(), errorHandle.errorType.warning)
                Return False
            Else
                'set default value is not processed
                If context.response().ContentEncoding Is Nothing Then
                    context.setcontentEncoding(encoding.utf8)
                End If
                If isemptystring(context.response().ContentType()) Then
                    context.setcontentType(network.http.contentType.texthtmlUtf8)
                End If
                If context.buff().Position > 0 Then
                    buff = New bytes(context.buff().GetBuffer(), 0, context.buff().Position())
                Else
                    buff = New bytes()
                End If
                m = nvchelper.tomap(context.response().Headers)
                cache.add(cachekey, New cache_t(buff, m))
            End If
        Else
            context.buff().Write(+buff, 0, buff.size())
            copyHttpHeaders(m, context.response().Headers)
        End If

        context.setstatuscode(Net.HttpStatusCode.OK, "OK")
        'if set retiredticks to npos, then cache for 1 day
        context.setcacheControl(True, ifoper(retiredticks() < 0, secondsToTicks(60 * 60 * 24), retiredticks()))
        Return True
    End Function

    Public Sub New(Optional ByVal forceUpdateQuery As String = defaultForceUpdate)
        cache = New cacheManager(Of String, cache_t)(True, cachesize(), retiredticks())
        copy(forceUpdate, ifoper(isemptystring(forceUpdateQuery), defaultForceUpdate, forceUpdateQuery))
    End Sub

    Public Overrides Sub initial(Optional ByVal config As configParser = Nothing)
        MyBase.initial(config)
        assert(Not cache Is Nothing, "cannot initial before cache has been initialized.")
        cache.clear()
    End Sub

    Protected Overrides Sub closeImpl()
        If Not cache Is Nothing Then
            cache.clear()
            cache = Nothing
        End If
        MyBase.closeImpl()
    End Sub
End Class
